namespace Bowlus.FrontEnd.RequestRouting.ApplicationDeployment
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Bowlus.Common;

    using Microsoft.WindowsAzure.ServiceRuntime;

    public class BasicDeploymentStrategy : IApplicationDeploymentStrategy
    {
        public IEnumerable<ApplicationDeploymentRequest> AddApplication(TenantDeployment newDeployment, IEnumerable<ApplicationHost> appHosts)
        {
            var appHostsList = appHosts.ToList();

            // Host with the smallest number of apps will receive the app deployment
            var targetHost = (from appHost in appHostsList
                              select appHost).OrderByDescending(ah => ah.Deployments.Count()).LastOrDefault();

            if (targetHost == null)
            {
                throw new ApplicationException("There has to be at least one host.");
            }

            var targetInstance =
                RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.FirstOrDefault(
                    i => targetHost.InstanceId.Equals(i.Id));

            if (targetInstance == null)
            {
                throw new ApplicationException(string.Format("No running instance with ID {0}.", targetHost.InstanceId));
            }

            return new List<ApplicationDeploymentRequest>
                {
                    new AddApplicationRequest(
                        newDeployment,
                        new ApplicationHost
                            {
                                DeploymentManagerEndpoint =
                                    targetInstance.InstanceEndpoints[Settings.AppHostRoleName].IPEndpoint,
                                InstanceId = targetInstance.Id,
                                Status = "Ready"
                            })
                };
        }

        public IEnumerable<ApplicationDeploymentRequest> RemoveApplication(TenantDeployment deploymentToRemove, IEnumerable<ApplicationHost> appHosts)
        {
            var appHostList = appHosts.ToList();

            var deploymentList =
                appHostList.SelectMany(ah => ah.Deployments.Select(d => new { Deployment = d, AppHostInstanceId = ah.InstanceId })).
                    ToList();

            var deployment = deploymentList.FirstOrDefault(dep => dep.Deployment.HostName.Equals(deploymentToRemove.HostName));

            if (deployment == null)
            {
                throw new ApplicationException(
                    string.Format("Application {0} is not found on a host", deploymentToRemove.HostName));
            }

            // Now construct a list of hosts with their deployments.
            //var appHosts = (from tenantDeployment in deploymentsList
            //                group tenantDeployment by tenantDeployment.AppHostInstanceId
            //                    into hostGroup
            //                    select
            //                        new
            //                        {
            //                            AppHostInstanceId = hostGroup.Key,
            //                            AppCount = hostGroup.Count(),
            //                            ApplicationDeployments =
            //                    deploymentsList.Where(dp => dp.HostName.Equals(hostGroup.Key))
            //                        }).ToList();

            // Will this make the number of apps on the host less than the average?
            //var appHost = appHosts.FirstOrDefault(h => h.AppHostInstanceId.Equals(appHostInstanceId));

            //if (appHost == null)
            //{
            //    throw new ApplicationException(string.Format("Application host {0} is not found.", appHostInstanceId));
            //}

            var newAvgAppsPerHost =
                Convert.ToInt16(Math.Floor((double) (deploymentList.Count - 1)/appHostList.Count()));

            var deploymentRequests = new List<ApplicationDeploymentRequest>();

            var hostDepMgrRemovingTheAppFrom =
                RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.First(i => i.Id.Equals(deployment.AppHostInstanceId)).InstanceEndpoints[Settings.AppHostRoleName];

            deploymentRequests.Add(
                new DeleteApplicationRequest(
                    deploymentToRemove,
                    new ApplicationHost
                    {
                        DeploymentManagerEndpoint = hostDepMgrRemovingTheAppFrom.IPEndpoint,
                        InstanceId = deployment.AppHostInstanceId,
                        Status = "Ready"
                    }));

            var appHost = appHostList.FirstOrDefault(ah => ah.InstanceId.Equals(deployment.AppHostInstanceId));
            if (appHost == null)
            {
                throw new ApplicationException(
                    string.Format("Application host {0} is not found.", deployment.AppHostInstanceId));
            }

            if (appHost.Deployments.Count() - 1 < newAvgAppsPerHost)
            {
                // Find the host with the largest number of apps
                var mostCrowdedHost =
                    (from host in appHostList where host.Deployments.Count().Equals(appHostList.Max(h => h.Deployments.Count())) select host).FirstOrDefault();

                if (mostCrowdedHost == null)
                {
                    throw new ApplicationException("Most crowded host is not found.");
                }

                // Will the number of apps be more than the average?
                if (mostCrowdedHost.Deployments.Count() > newAvgAppsPerHost)
                {
                    // Pick a victim and create a move request
                    var appToMove = mostCrowdedHost.Deployments.First();

                    var fromHostDepMgrEndPoint =
                        RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.First(
                            i => i.Id.Equals(mostCrowdedHost.InstanceId)).InstanceEndpoints[Settings.AppHostRoleName];

                    deploymentRequests.Add(
                        new MoveApplicationRequest(
                            appToMove,
                            new ApplicationHost
                            {
                                DeploymentManagerEndpoint = fromHostDepMgrEndPoint.IPEndpoint,
                                InstanceId = mostCrowdedHost.InstanceId,
                                Status = "Ready"
                            },
                            new ApplicationHost
                            {
                                DeploymentManagerEndpoint = hostDepMgrRemovingTheAppFrom.IPEndpoint,
                                InstanceId = appHost.InstanceId,
                                Status = "Ready"
                            }));
                }
            }

            return deploymentRequests;
        }

        public IEnumerable<ApplicationDeploymentRequest> InitializeDeployments(IEnumerable<ApplicationHost> applicationHosts, IEnumerable<TenantDeployment> deployments)
        {
            var i = 0;
            var appHosts = applicationHosts.ToList();
            var numberOfAppHosts = appHosts.Count();

            if (0 == numberOfAppHosts)
            {
                return new ApplicationDeploymentRequest[] { };
            }

            var deploymentRequests = //new List<ApplicationDeploymentRequest>();
                deployments.Select(
                    tenantDeployment =>
                    new AddApplicationRequest(tenantDeployment, applicationHosts.ElementAt(i++ % numberOfAppHosts))).ToList();

            return deploymentRequests;
        }

        public IEnumerable<ApplicationDeploymentRequest> AddHost(ApplicationHost newHost, IEnumerable<ApplicationHost> appHosts)
        {
            var appHostList = appHosts.ToList();

            var deployedApplications = appHostList.Sum(ah => ah.Deployments.Count());

            // find the new average number of deployments with the new addition
            var newAvgNumberOfDeployments =
                Convert.ToInt16(Math.Ceiling((double) deployedApplications/(appHostList.Count + 1)));

            var deploymentRequests = new List<ApplicationDeploymentRequest>();

            // Move that many apps to the new host
            var movedApps = 0;
            while (movedApps < newAvgNumberOfDeployments)
            {
                foreach (var host in appHostList)
                {
                    var appCountToMove = host.Deployments.Count() > newAvgNumberOfDeployments
                                             ? host.Deployments.Count() - newAvgNumberOfDeployments
                                             : 0;

                    var appsToMove = host.Deployments.Take(appCountToMove);

                    var fromHostDepMgrEndPoint =
                        RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.First(
                            ins => ins.Id.Equals(host.InstanceId)).InstanceEndpoints[Settings.AppHostRoleName];

                    var fromHost = new ApplicationHost
                    {
                        DeploymentManagerEndpoint = fromHostDepMgrEndPoint.IPEndpoint,
                        InstanceId = host.InstanceId,
                        Status = "Ready"
                    };

                    foreach (var tenantDeployment in appsToMove)
                    {
                        deploymentRequests.Add(new MoveApplicationRequest(tenantDeployment, fromHost, newHost));
                        movedApps++;
                    }
                }
            }

            return deploymentRequests;
        }

        public IEnumerable<ApplicationDeploymentRequest> RemoveHost(ApplicationHost hostToRemove, IEnumerable<ApplicationHost> appHosts)
        {
            var appHostList = appHosts.ToList();

            var i = 0;

            var appHost = appHostList.First(ah => ah.InstanceId.Equals(hostToRemove.InstanceId));

            if (appHost == null)
            {
                throw new ApplicationException(
                 string.Format("Host {0} is not found.", hostToRemove.InstanceId));
            }

            var remaningHosts = (from host in appHostList
                                where !host.InstanceId.Equals(hostToRemove.InstanceId)
                                select host).ToList();

            var deploymentRequests = 
                appHost.Deployments.Select(
                    tenantDeployment =>
                    new AddApplicationRequest(tenantDeployment, remaningHosts.ElementAt(i++ % remaningHosts.Count))).ToList();

            return deploymentRequests;
        }
    }
}